/*
 * Copyright (c) 2005-2012 Imperas Software Ltd., www.imperas.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied.
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

#include <stdio.h>
#include <string.h>

#include "icm/icmCpuManager.h"

#define SIM_ATTRS (ICM_ATTR_DEFAULT)

//
// Main routine
//
int main(int argc, char **argv) {

    // Check arguments for application to load
    if(argc!=2) {
        // incorrect arguments
        icmPrintf("Usage : %s <application name>\n", argv[0]);
        return -1;
    }
    const char *appName = argv[1];

    // initialize OVPsim, enabling verbose mode to get statistics at end
    // of execution
    icmInit(ICM_VERBOSE|ICM_STOP_ON_CTRLC, 0, 0);

    const char *v850Model    = icmGetVlnvString(NULL, "renesas.ovpworld.org", "processor", "v850", "1.0", "model");
    const char *v850Semihost = icmGetVlnvString(NULL, "renesas.ovpworld.org", "semihosting", "v850Newlib", "1.0", "model");
	
	// create the processor bus
    icmBusP bus = icmNewBus("busMain", 32);
		
	////////////////////////////////////////////////////////////////////////////
    // Memory
    ////////////////////////////////////////////////////////////////////////////

    // create two memory regions mapping all memory except the DMAC registers
    icmMemoryP mem1 = icmNewMemory("mem1", ICM_PRIV_RWX, 0x7fffffff);

    // connect memories to the bus
    icmConnectMemoryToBus(bus, "sp", mem1, 0x00000000);
    
	////////////////////////////////////////////////////////////////////////////
    // Processor
    ////////////////////////////////////////////////////////////////////////////
	// create a processor instance
    icmProcessorP processor = icmNewProcessor(
        "CPU1",             // CPU name
        "v850",             // CPU type
        0,                  // CPU cpuId
        0,                  // CPU model flags
        32,                 // address bits
        v850Model,          // model file
        "modelAttrs",       // morpher attributes
        SIM_ATTRS,          // attributes
        0,                  // user-defined attributes
        v850Semihost,       // semi-hosting file
        "modelAttrs"        // semi-hosting attributes
    );

	// connect the processor instruction and data busses to the bus
    icmConnectProcessorBusses(processor, bus, bus);
	
	////////////////////////////////////////////////////////////////////////////
    // INTC Peripheral
    ////////////////////////////////////////////////////////////////////////////
    // instantiate the peripheral
	// const char* intcPath = icmGetVlnvString(NULL, "renesas.ovpworld.org", "peripheral", "intc", "1.0", "pse.pse"); // get the path to intc pse.pse file
    // icmPseP intc = icmNewPSE("interruptController", intcPath, NULL, NULL, NULL);
	icmPseP intc = icmNewPSE("interruptController", "intc/pse/pse.pse", NULL, NULL, NULL);
	icmSetPSEdiagnosticLevel(intc, 3);
    
	//connect the INTC slave ports on the bus and define the address range it occupies
    icmConnectPSEBus(intc, bus, "imr", False, 0x80000000, 0x8000000f);
	icmConnectPSEBus(intc, bus, "icr", False, 0x80000010, 0x800000fb);
	icmConnectPSEBus(intc, bus, "isesf", False, 0x800000fc, 0x800000ff);
	icmConnectPSEBus(intc, bus, "intm", False, 0x80000100, 0x80000107);

	
	////////////////////////////////////////////////////////////////////////////
    // CLKGEN Peripheral
    ////////////////////////////////////////////////////////////////////////////
    // instantiate the peripheral
	const char* clkgenPath = icmGetVlnvString(NULL, "renesas.ovpworld.org", "peripheral", "clkgen", "1.0", "pse.pse"); // get the path to intc pse.pse file
	icmPseP clkgen = icmNewPSE("clockGenerator", clkgenPath, NULL, NULL, NULL);
	
	//connect the CLKGEN slave ports on the bus and define the address range it occupies
    icmConnectPSEBus(clkgen, bus, "CLKGENP0", False, 0x80000108, 0x80000108);
	icmConnectPSEBus(clkgen, bus, "CLKGENP1", False, 0x80000109, 0x80000109);
	
	
	////////////////////////////////////////////////////////////////////////////
    // INTERRUPTER Peripheral
    ////////////////////////////////////////////////////////////////////////////
    // instantiate the peripheral
	icmPseP interrupter = icmNewPSE("interrupter", "peripheral/pse.pse", NULL, NULL, NULL);
	

	/////////////////////////////////////////////////////////////////////////
	// CONNECTIONS
	/////////////////////////////////////////////////////////////////////////
	icmNetP reset = icmNewNet("RESET");
    icmConnectProcessorNet( processor, reset, "reset", ICM_INPUT);
    icmConnectPSENet(intc, reset, "reset", ICM_OUTPUT);

    icmNetP nmi0 = icmNewNet("NMI0");
    icmConnectProcessorNet( processor, nmi0, "nmi0", ICM_INPUT);
    icmConnectPSENet(intc, nmi0, "nmi0", ICM_OUTPUT);

	// These ports may be available in other versions of v850
    // icmNetP nmi1 = icmNewNet("NMI1");
    // icmConnectProcessorNet( processor, nmi1, "nmi1", ICM_INPUT);
    // icmConnectPSENet(intc, nmi1, "nmi1", ICM_OUTPUT);

    // icmNetP nmi2 = icmNewNet("NMI2");
    // icmConnectProcessorNet( processor, nmi2, "nmi2", ICM_INPUT);
    // icmConnectPSENet(intc, nmi2, "nmi2", ICM_OUTPUT);

    icmNetP intp = icmNewNet("INTP");
    icmConnectProcessorNet( processor, intp, "intp", ICM_INPUT);
    icmConnectPSENet(intc, intp, "intp", ICM_OUTPUT);

    icmNetP intack = icmNewNet("INTACK");
    icmConnectProcessorNet( processor, intack, "intack", ICM_OUTPUT);
    icmConnectPSENet(intc, intack, "intack", ICM_INPUT);
	
	
	//connect interrupter with interrupt controller
	icmNetP intpInterrupter = icmNewNet("intpInterrupter");
    icmConnectPSENet(interrupter, intpInterrupter, "intp", ICM_OUTPUT);
	icmConnectPSENet(intc, intpInterrupter, "INT_00", ICM_INPUT);
   
   
	// load the application executable file into processor memory space
    if(!icmLoadProcessorMemory(processor, appName, False, False, True)) {
        return -1;
    }
   
    // run simulation
	icmProcessorP stoppedProcessor = icmSimulatePlatform();
	icmTerminate();
    // was simulation interrupted or did it complete
    if(stoppedProcessor && (icmGetStopReason(stoppedProcessor)==ICM_SR_INTERRUPT)) {
        icmPrintf("*** simulation interrupted\n");
    }

    return 0;
}
